#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <semaphore.h>
#include <pthread.h>

#include "shm.h"
#ifdef DMALLOC
#include <imp/dmalloc.h>
#endif
#define SHM_NAME 		"imp_deubg_shm"
#define SHM_OPEN_FLAG 	(O_RDWR|O_CREAT)
#define SHM_OPEN_MODE 	(0777)
#define SHM_SIZE 		20480

#define SEM_TOS_NAME "imp_deubg_sem_tos"
#define SEM_TOC_NAME "imp_deubg_sem_toc"

#define DP_INFO(...) { printf("info: %s,%d", __func__, __LINE__); printf(__VA_ARGS__); }
#define DP_WARN(...) { printf("warn: %s,%d", __func__, __LINE__); printf(__VA_ARGS__); }
#define DP_ERR(...) { printf("err: %s,%d ", __func__, __LINE__); printf(__VA_ARGS__); }

typedef struct {
	sem_t *psem_tos;
	sem_t *psem_toc;
	int shmfd;
	void *buffer;
	pthread_t tid;
	int (*cb)(void *buf, int msize);
} shm_attr;

static shm_attr *psa = NULL;

void shm_thread(void *arg);

int shm_init(void)
{
	int ret = -1;
	if (NULL == psa) {
		psa = malloc(sizeof(shm_attr));
		if (NULL == psa) {
			DP_ERR("malloc err \n");
			goto err_malloc;
		}
		memset(psa, sizeof(shm_attr), 0);
	} else {
		DP_WARN("shm init already\n");
		goto done;
	}
	/* init shm */
	psa->shmfd = shm_open(SHM_NAME, SHM_OPEN_FLAG, SHM_OPEN_MODE);
	if (-1 == psa->shmfd) {
		DP_ERR("shm_open err %s, %s\n", strerror(errno), SHM_NAME);
		goto err_shm_open;
	}
	ret = ftruncate(psa->shmfd, SHM_SIZE);
	if (-1 == ret) {
		DP_ERR("ftruncate err %s\n", strerror(errno));
		goto err_ftruncate;
	}
	psa->buffer = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, psa->shmfd, 0);
	if (MAP_FAILED == psa->buffer) {
		DP_ERR("mmap err %s\n", strerror(errno));
		goto err_mmap;
	}

	/* init sem */
	psa->psem_tos = sem_open(SEM_TOS_NAME, O_CREAT | O_EXCL, 0666, 0);
	if (SEM_FAILED == psa->psem_tos) {
		if (EEXIST == errno) {
			sem_unlink(SEM_TOS_NAME);
			psa->psem_tos = sem_open(SEM_TOS_NAME, O_CREAT | O_EXCL, 0666, 0);
			if (SEM_FAILED == psa->psem_tos) {
				DP_ERR("sem_open tos err %s\n", strerror(errno));
				goto err_sem_open_tos;
			}
		} else {
			DP_ERR("sem_open tos err %s\n", strerror(errno));
			goto err_sem_open_tos;
		}
	}

	psa->psem_toc = sem_open(SEM_TOC_NAME, O_CREAT | O_EXCL, 0666, 0);
	if (SEM_FAILED == psa->psem_toc) {
		if (EEXIST == errno) {
			sem_unlink(SEM_TOC_NAME);
			psa->psem_toc = sem_open(SEM_TOC_NAME, O_CREAT | O_EXCL, 0666, 0);
			if (SEM_FAILED == psa->psem_toc) {
				DP_ERR("sem_open tos err %s\n", strerror(errno));
				goto err_sem_open_toc;
			}
		} else {
			DP_ERR("sem_open tos err %s\n", strerror(errno));
			goto err_sem_open_toc;
		}
	}

	/* init thread */
	ret = pthread_create(&psa->tid, NULL, (void*)shm_thread, psa);
	if (0 != ret) {
		DP_ERR("pthread_create \n");
		goto err_pthread_create;

	}
done:
	return 0;
err_pthread_create:
err_sem_open_toc:
err_sem_open_tos:
err_mmap:
err_ftruncate:
err_shm_open:
	if (NULL != psa) {
		free(psa);
		psa = NULL;
	}
err_malloc:
	return -1;
}

int shm_deinit(void)
{
	int ret = -1;
	if (NULL == psa) {
		DP_WARN("shm_deinit already \n");
		goto done;
	}

	/* deinit thread */
	ret = pthread_cancel(psa->tid);
	if (ret != 0) {
		DP_ERR("pthread_cancel %s\n", strerror(errno));
		goto err_pthread_cancel;
	}
	pthread_join(psa->tid, NULL);

	/* deinit shm */
	ret = munmap(psa->buffer, SHM_SIZE);
	if (-1 == ret) {
		DP_ERR("munmap err %s\n", strerror(errno));
		goto err_munmap;
	}
	ret = shm_unlink(SHM_NAME);
	if (-1 == ret) {
		DP_ERR("shm_unlink err %s\n", strerror(errno));
		goto err_shm_unlink;
	}

	/* deinit sem */
	if (0 != sem_close(psa->psem_tos)) {
		DP_ERR("sem_close tos err %s\n", strerror(errno));
		goto err_sem_tos_close;
	}
	if (0 != sem_unlink(SEM_TOS_NAME)) {
		DP_ERR("sem_unlink tos err %s\n", strerror(errno));
		goto err_sem_tos_unlink;
	}

	if (0 != sem_close(psa->psem_toc)) {
		DP_ERR("sem_close toc err %s\n", strerror(errno));
		goto err_sem_toc_close;
	}
	if (0 != sem_unlink(SEM_TOC_NAME)) {
		DP_ERR("sem_unlink toc err %s\n", strerror(errno));
		goto err_sem_toc_unlink;
	}

	if (NULL != psa) {
		free(psa);
		psa = NULL;
	}

done:
	return 0;
err_sem_toc_unlink:
err_sem_toc_close:
err_sem_tos_unlink:
err_sem_tos_close:
err_shm_unlink:
err_munmap:
err_pthread_cancel:
	return -1;
}

int shm_register_cb(int (*cb)(void *buf, int msize))
{
	if (NULL == psa) {
		DP_ERR("psa == NULL\n");
		goto err_null_psa;
	}
	psa->cb = cb;
	return 0;
err_null_psa:
	return -1;
}

void shm_thread(void *arg)
{
	int ret = -1;
	shm_attr *psa = (shm_attr *)arg;
	prctl(PR_SET_NAME, __func__);
	while(1) {
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
		ret = sem_wait(psa->psem_tos);
		if (0 != ret) {
			DP_ERR("sem_wait %s\n", strerror(errno));
		}
		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
#if 0
		ret = msync(psa->buffer, SHM_SIZE, MS_SYNC|MS_INVALIDATE);
		if (0 != ret) {
			DP_ERR("msync %s\n", strerror(errno));
		}
#endif
		/* to do */
		if (NULL != psa->cb) {
			psa->cb(psa->buffer, SHM_SIZE);
		}
		/* end */
#if 0
		ret = msync(psa->buffer, SHM_SIZE, MS_SYNC|MS_INVALIDATE);
		if (0 != ret) {
			DP_ERR("msync %s\n", strerror(errno));
		}
#endif
		ret = sem_post(psa->psem_toc);
		if (0 != ret) {
			DP_ERR("sem_post %s\n", strerror(errno));
		}

	}
}

